/***************************************************************
*
*  Synopsis:   Certificat handling
*    	    
*  Description:
*     	    
*  History:
*      	         dietrich - Jan 18, 2012:  Created.
*
*
***************************************************************/
/*
 *   init_x509_certificate()
 *   free_x509_certficate ()
 *   int parse_x509_certficate(const unsigned char * buffer,
 *			  const unsigned int certificate_length,
 *			  signed_x509_certficate * parsed_certificate)
 *
 * 
 *
 *  TODO:
 *   + interface with RSA
 *   + Signature verification
 */
  

/* $Id: x509.c,v 1.1 2012/01/18 08:20:56 dietrich Exp $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <gmp.h>
#include <time.h>

#include "rsa.h"
#include "base64.h"
#include "asn1.h"
#include "x509.h"
#include "digest.h"
#include "utils.h"

// rsa key type and api.

void rsa_free (rsa_key_t * key)
{
  //TBD
}

static void print_huge( mpz_t h)
{
    mpz_printf16("", h);
}


void init_x509_certificate (signed_x509_certficate * certificate)
{
  x509_certficate * ptbs  = &certificate->tbsCertificate;
  mpz_init_set_ui(ptbs->serialNumber,1);
  memset (&ptbs->issuer, 0, sizeof(name));
  memset (&ptbs->subject, 0,sizeof(name));
  // TODO: RSA API:   rsa_init()
  rsa_init(&ptbs->subjectPublicKeyInfo.rsa_public_key);
  mpz_init_set_ui(certificate->signature_value, 0);
  ptbs->certificate_authority = 0;
  certificate->hash_len=0;
  certificate->hash=NULL;
}

static free_x500_name (name * x500_name)
{
	if (x500_name->idAtCommonName)  free(x500_name->idAtCommonName);
	if (x500_name->idAtStateOfProvinceName)  free(x500_name->idAtStateOfProvinceName);
	if (x500_name->idAtOrganizationalUnitName)  free(x500_name->idAtOrganizationalUnitName);
	if (x500_name->idAtLocalityName)  free(x500_name->idAtLocalityName);
	if (x500_name->idAtCountryName)  free(x500_name->idAtCountryName);
	if (x500_name->idAtOrganizationName)  free(x500_name->idAtOrganizationName);		
}


void free_x509_certficate (signed_x509_certficate * certificate)
{
	mpz_clear(certificate->tbsCertificate.serialNumber);
	free_x500_name(&certificate->tbsCertificate.issuer);
	free_x500_name(&certificate->tbsCertificate.subject);
	mpz_clear(certificate->signature_value);
	rsa_free(&certificate->tbsCertificate.subjectPublicKeyInfo.rsa_public_key);
	if (certificate->hash)
	    free(certificate->hash);
}



int parse_huge ( mpz_t huge, struct asn1struct * pasn)
{
  // content of huge is  pasn->data, of length pasn->size
  mpz_import_buf(pasn->data, pasn->length, huge);
  return 0;
}


static const unsigned char OID_md5withRSA[] =
{ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 };

static const unsigned char OID_sha1withRSA[] =
{ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 };


int parse_algorithm_identifier ( signatureAlgorithIdentifier *signature, struct asn1struct * pasn)
{
  struct asn1struct * oid = pasn->children;

  if (!memcmp(oid->data, OID_md5withRSA, oid->length)) {
    *signature = md5WithRsaEncryption;
  } else if (!memcmp(oid->data, OID_sha1withRSA, oid->length)) {
    *signature = sha1WithRsaEncryption;
  } else {
    int k;
    fprintf(stderr,"Unsupported or unrecognized algorithm identifier OID :");
    for(k=0; k<oid->length; k++)
      fprintf(stderr,"%.02x ",oid->data[k]);
    fprintf(stderr,"\n");
    return 0;
  }
  return 0;
}



static unsigned char OID_idAtCommonName[] = { 0x55, 0x04, 0x03 };
static unsigned char OID_idAtCountryName[] = { 0x55, 0x04, 0x06 };
static unsigned char OID_idAtLocalityName[] = { 0x55, 0x04, 0x07 };
static unsigned char OID_idAtStateOfProvinceName[] = { 0x55, 0x04, 0x08 };
static unsigned char OID_idAtOrganizationName[] = { 0x55, 0x04, 0x0a };
static unsigned char OID_idAtOrganizationalUnitName[] = { 0x55, 0x04, 0x0b };

int parse_name ( name * pn, struct asn1struct * pasn)
{
  struct asn1struct * typeValuePair;
  struct asn1struct * typeValuePairSequence;
  struct asn1struct * type;
  struct asn1struct * value;

  pn->idAtCountryName = NULL;
  pn->idAtStateOfProvinceName = NULL;
  pn->idAtLocalityName = NULL;
  pn->idAtOrganizationName = NULL;
  pn->idAtOrganizationalUnitName = NULL;
  pn->idAtCommonName = NULL;

  typeValuePair = pasn->children;
  while (typeValuePair) {
    typeValuePairSequence = typeValuePair->children;
    type = typeValuePairSequence->children;
    value = type->next;

#define  COPY_NAME_ATTRIBUTE(ATTR)		\
    if (!memcmp(type->data, OID_##ATTR, type->length)) { \
      pn->ATTR = (char*)malloc(value->length+1); \
      memcpy(pn->ATTR, value->data, value->length); \
      pn->ATTR[value->length]=0; \
    } else 
    
/*    if (!memcmp(type->data, OID_idAtCountryName, type->length)) {
      pn->idAtCountryName = (char*)malloc(value->length+1);
      memcpy(pn->idAtCountryName, value->data, value->length);
      pn->idAtCountryName[value->length]=0;
    }
*/
    
    COPY_NAME_ATTRIBUTE(idAtCountryName);
    COPY_NAME_ATTRIBUTE(idAtStateOfProvinceName);
    COPY_NAME_ATTRIBUTE(idAtLocalityName);
    COPY_NAME_ATTRIBUTE(idAtOrganizationName);
    COPY_NAME_ATTRIBUTE(idAtOrganizationalUnitName);
    COPY_NAME_ATTRIBUTE(idAtCommonName);
    {
      int k;

      // This is advisory not a problem
#if 0      
      fprintf(stderr,"Skipping unrecognized or unsupported name token OID of ");

      for (k=0; k<type->length; k++)
	      fprintf(stderr,"%.02x ",type->data[k]);
      fprintf(stderr,"\n");
#endif
      printf("Skipping unrecognized or unsupported name token OID of ");
      print_oid(type->data, type->length);
      printf("\n");
      
    }
    typeValuePair = typeValuePair->next;
  }
  return 0;
}

int parse_validity (validity_period *pt, struct asn1struct * pasn)
{
  struct asn1struct * not_before;
  struct asn1struct * not_after;
  struct tm not_before_tm;
  struct tm not_after_tm;

  not_before = pasn->children;
  not_after = not_before->next;

  // ASN1convert time to time_t
  if (sscanf((char*)not_before->data,"%2d%2d%2d%2d%2d%2d",
	     &not_before_tm.tm_year,&not_before_tm.tm_mon,&not_before_tm.tm_mday,
	     &not_before_tm.tm_hour,&not_before_tm.tm_min,&not_before_tm.tm_sec)<6)
  {
    fprintf(stderr,"Error parsing 'not before', malformed date\n");
    return 7;
  }
  if (sscanf((char*)not_after->data,"%2d%2d%2d%2d%2d%2d",
	     &not_after_tm.tm_year,&not_after_tm.tm_mon,&not_after_tm.tm_mday,
	     &not_after_tm.tm_hour,&not_after_tm.tm_min,&not_after_tm.tm_sec)<6)
  {
    fprintf(stderr,"Error parsing 'not after', malformed date\n");
    return 7;
  }

  not_before_tm.tm_year += 100;
  not_before_tm.tm_mon -= 1;
  not_after_tm.tm_year += 100;
  not_after_tm.tm_mon -= 1;

  // TODO: account for TZ information on end.
  pt->notBefore = mktime(&not_before_tm);
  pt->notAfter = mktime(&not_after_tm);

  return 0;
}

static const unsigned char OID_RSA[] =
{ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01};

int parse_public_key_info ( public_key_info * keyinfo,  struct asn1struct * pasn)
{
    struct asn1struct *oid;
    struct asn1struct *public_key;
    struct asn1struct public_key_value;
    
    oid = pasn->children->children;
    public_key = pasn -> children -> next;

    // The public key is a bit string encoding yet another ASN.1 DER-encoded value.
    // Skip over the 0 byte in the public key

    if (asn1parse(public_key->data+1, public_key->length-1, &public_key_value)) {
	fprintf(stderr, "Error: public key note is malformred (not ASN.1 DER-encoded)\n");
	return 5;
    }

    if (!memcmp(oid->data, &OID_RSA, sizeof(OID_RSA))) {
	keyinfo->algorithm = rsa;
	parse_huge(keyinfo->rsa_public_key.modulus, public_key_value.children);
	parse_huge(keyinfo->rsa_public_key.exponent, public_key_value.children->next);
	// Most times the response includes a trailing 0...
    } else {
	fprintf(stderr,"Error: unsupported OID in public key info.\n");
	return 7;
    }

    asn1free( &public_key_value);
    return 0;
}

int asn1_get_bit(const int length, const unsigned char *bit_string, const int bit)
{
    if (bit>((length-1)*8))
	return 0;
    else
	return bit_string[1+(bit/8)] & (0x80 >> (bit%8));
}



static const unsigned char OID_keyUsage[] = {0x55, 0x1D, 0x0F};
#define BIT_CERT_SIGNER 5

int parse_extension (x509_certficate *certificate, struct asn1struct * pasn)
{
    struct asn1struct *oid;
    struct asn1struct *critical;
    struct asn1struct *data;

    oid = pasn->children;
    critical = oid->next;
    if (critical->tag == ASN1_BOOLEAN)
    {
	data = critical->next;
    } else {
	data = critical;
	critical = NULL;
    }

    if (!memcmp(oid->data, OID_keyUsage, oid->length)) {
	struct asn1struct key_usage_bit_string;
	asn1parse(data->data, data->length, &key_usage_bit_string);
	if (asn1_get_bit(key_usage_bit_string.length, key_usage_bit_string.data, BIT_CERT_SIGNER)) {
	    certificate->certificate_authority = 1;
	}
	asn1free(&key_usage_bit_string);
    }
    return 0;
}


int parse_extensions (x509_certficate * certificate, struct asn1struct *pasn)
{
    pasn = pasn->children->children;
    while (pasn) {
	if (parse_extension(certificate, pasn))
	    return 1;
	else
	    pasn = pasn->next;
    }
    return 0;
}



int parse_signature_value(signed_x509_certficate * certificate, struct asn1struct *pasn)
{
	parse_huge(certificate->signature_value, pasn);
	return 0;
}
			

int parse_tbs_certificate (signed_x509_certficate *certificate, struct asn1struct * pasn)
{
	struct asn1struct * version;
	struct asn1struct * serialNumber;
	struct asn1struct * signatureAlgorithIdentifier;
	struct asn1struct * issuer;
	struct asn1struct * validity;
	struct asn1struct * subject;
	struct asn1struct * publicKeyInfo;
	struct asn1struct * extensions;


	x509_certficate * pcert = &certificate->tbsCertificate;
	
	// VERSION NUMBER.
	// Figure our if there is an explicit version number.
	version = pasn->children;
	if (version->tag==0 && version->tag_class == ASN1_CONTEXT_SPECIFIC) {
		struct asn1struct * versionNumber = version->children;
		// version is only one byte.
		pcert->version = *versionNumber->data +1;
		serialNumber = version->next;
	} else {
		pcert->version = 1;
		serialNumber = version;
	}

	signatureAlgorithIdentifier = serialNumber->next;
	issuer = signatureAlgorithIdentifier->next;
	validity = issuer->next;
	subject = validity->next;
	publicKeyInfo = subject->next;
	extensions = publicKeyInfo->next;

	if (parse_huge(pcert->serialNumber, serialNumber)) {
		return 2;
	}
	if (parse_algorithm_identifier(&pcert->signature, signatureAlgorithIdentifier)) {
		return 3;
	}
	if (parse_name(&pcert->issuer, issuer)) {
		return 4;
	}
	if (parse_validity(&pcert->validity, validity)) {
		return 5;
	}
	if (parse_name(&pcert->subject, subject)) {
		return 6;
	}
	if (parse_public_key_info(&pcert->subjectPublicKeyInfo, publicKeyInfo)){
		return 7;
	}
	if (extensions && parse_extensions(pcert, extensions)) {
		return 8;
	}
	return 0;
}


int parse_x509_certficate(const unsigned char * buffer,
			  const unsigned int certificate_length,
			  signed_x509_certficate * parsed_certificate)
{

	struct asn1struct certificate;
	struct asn1struct *tbsCertificate;
	struct asn1struct *algorithmIdentifier;
	struct asn1struct *signature_value;

	// Read the cert in an ans1 tree structures
	asn1parse (buffer, certificate_length, &certificate);

	tbsCertificate = certificate.children;
	algorithmIdentifier = tbsCertificate->next;
	signature_value = algorithmIdentifier->next;


	if (parse_tbs_certificate(parsed_certificate, tbsCertificate)) {
		fprintf(stderr, "Error trying to parse TBS certificate\n");
		return 42;
	}

	if (parse_algorithm_identifier(&parsed_certificate->algorithm, algorithmIdentifier)) {
		fprintf(stderr, "Error trying to parse algorithm identifier \n");
		return 42;
	}

	if (parse_signature_value(parsed_certificate, signature_value)) {
		fprintf(stderr, "Error parsing signature value\n");
		return 42;
	}


	// compute the signature of tbsCertificate and store it into the certifcate.hash
	digest_ctx ctx;    
	switch(parsed_certificate->algorithm)
	{
	    int k;                
	case md5WithRsaEncryption:
	    print_huge(parsed_certificate->signature_value);
	    new_md5_digest(&ctx);
	    update_digest(&ctx,tbsCertificate->data, tbsCertificate->length);
	    finalize_digest(&ctx);
	    parsed_certificate->hash_len = ctx.hash_len;
	    parsed_certificate->hash = (int *)malloc(ctx.hash_len*sizeof(int));
	    for (k=0; k < ctx.hash_len; k++)
		parsed_certificate->hash[k] = ctx.hash[k];

	    print_digest("calcfrom tbs cert",&ctx);
	    break;
	case sha1WithRsaEncryption:
	    print_huge(parsed_certificate->signature_value);
	    new_sha1_digest(&ctx);
	    update_digest(&ctx,tbsCertificate->data, tbsCertificate->length);
	    finalize_digest(&ctx);
	    /* save tbs in file */
	    save_data_file("tbscert.bin",(void*)tbsCertificate->data, tbsCertificate->length);
		    
	    parsed_certificate->hash_len = ctx.hash_len;
	    parsed_certificate->hash = (int *)malloc(ctx.hash_len*sizeof(int));

	    for (k=0; k < ctx.hash_len; k++)
		parsed_certificate->hash[k] = ctx.hash[k];

	    print_digest("calcfrom tbs cert",&ctx);

	    break;
	}
	
	asn1free(&certificate);
	return 0;
}


// TODO
#if 1

/*
 * An RSA signature is an ASN.1 DER encoded PKCS-7 structure including the OID of the
 * signature algorithm (again), and the signature value
 */
static int validate_certificate_rsa(signed_x509_certficate * certificate,
				    rsa_key_t * public_key)
{
    unsigned char *pkcs7_signature_decrypted;
    int pkcs7_signature_len;
    struct asn1struct pkcs7_signature;
    struct asn1struct * hash_value;
    int valid = 0;

    char * buf;
    int bufsize;
    int ret;

    bufsize = mpz_export_buf(NULL,0, certificate->signature_value);
    buf = (char*)malloc(bufsize);
    if (buf==NULL) {
	printf("validate_certificate_rsa: malloc failed for %d bytes\n",bufsize);
	return -1;
    }
    ret = mpz_export_buf(buf, bufsize, certificate->signature_value);
    if (ret != bufsize) {
	printf("validate_certificate_rsa: mpz_export_buf failed, ret %d\n",ret);
	return -1;
    }

    pkcs7_signature_len = rsa_decrypt(buf, bufsize, &pkcs7_signature_decrypted, public_key);

    if (pkcs7_signature_len == -1) {
	fprintf(stderr,"Unable to decode signature value.\n");
	return valid;
    }

    if (asn1parse(pkcs7_signature_decrypted, pkcs7_signature_len, &pkcs7_signature)) {
	fprintf(stderr,"Unable to parse signature\n");
	return valid;
    }

    hash_value = pkcs7_signature.children->next;
    memdumpex("Decrypted hash","",(char*)hash_value->data,hash_value->length,80);
    memdumpex("Computed hash ","",(char*)certificate->hash,hash_value->length,80);    
    if (memcmp(hash_value->data, certificate->hash, certificate->hash_len)) {
	valid = 0;
    } else {
	valid = 1;
    }

    asn1free(&pkcs7_signature);
    return valid;
}
#endif


#define IFNAME(a)  ((a)?(a):"?")

static void output_x500_name (name *x500_name)
{
    printf("C=%s/ST=%s/L=%s/O=%s/OU=%s/CN=%s\n",
	   IFNAME(x500_name->idAtCountryName),
	   IFNAME(x500_name->idAtStateOfProvinceName),
	   IFNAME(x500_name->idAtLocalityName),
	   IFNAME(x500_name->idAtOrganizationName),
	   IFNAME(x500_name->idAtOrganizationalUnitName),
	   IFNAME(x500_name->idAtCommonName));
}



static void display_x509_certificate(signed_x509_certficate *certificate)
{
    x509_certficate * psc = &certificate->tbsCertificate;
    printf("Certficate:\n");
    printf("Version: %d\n",psc->version);
    printf("Serial Number:");
    print_huge(psc->serialNumber);
    printf("Issuer:");
    output_x500_name(&psc->issuer);
    printf("Subject:");
    output_x500_name(&psc->subject);
    printf("not before: %s", asctime(gmtime(&psc->validity.notBefore)));
    printf("not after: %s",  asctime(gmtime(&psc->validity.notAfter)));
    printf("Public key algorithm: ");
    switch(psc->subjectPublicKeyInfo.algorithm)
    {
    case rsa:
	printf("modulus: ");
	print_huge(psc->subjectPublicKeyInfo.rsa_public_key.modulus);
	printf("exponent:");
	print_huge(psc->subjectPublicKeyInfo.rsa_public_key.exponent);	
	break;
    case dh:
	printf("DH\n");
	break;
    default:
	printf("?\n");
	break;
    }
    printf("Signature algorithm: ");
    switch(certificate->algorithm)
    {
    case md5WithRsaEncryption:
	printf("MD5 with RSA encryption\n");
	break;
    case sha1WithRsaEncryption:
	printf("SHA-1 with RSA encryption\n");
	break;
    }
    printf("Signature value: ");
    switch(certificate->algorithm)
    {
    case md5WithRsaEncryption:
    case sha1WithRsaEncryption:
	print_huge(certificate->signature_value);
	break;
    }
//    printf("\n");

    if (psc->certificate_authority) {
	printf("is a CA\n");
    } else
	printf("is not a CA\n");

    validate_certificate_rsa(certificate, &certificate->tbsCertificate.subjectPublicKeyInfo.rsa_public_key);
#if 0
    if (certificate->valid)
            printf("**** Signature does match **** \n");	
    else
            printf("!!!! Signature does not match !!!! \n");  
#endif
}



#define TEST_X509
#ifdef TEST_X509
int main(int argc, char *argv[])
{
	int certificate_file;
	struct stat certificate_file_stat;
	char *buffer, *bufptr;
	int buffer_size;
	int bytes_read;
	int error_code;
	signed_x509_certficate certificate;

	if (argc<3) {
		fprintf(stderr,"usage: x509 [-pem|-der] [certificate_file]\n");
		exit(1);
	}
	
	if ((certificate_file=open(argv[2],O_RDONLY))==-1) {
		perror("Unable to open certficate file");
		return 1;
	}
		
	// reading the whole thin in memory
	if (fstat(certificate_file, &certificate_file_stat)) {
		perror("Unable to stat certificate file");
		return 1;
	}

	buffer_size = certificate_file_stat.st_size;
	if (!(buffer = (char*)malloc(buffer_size))) {
		perror("Not enough memory");
		return 1;
	}
	bufptr = buffer;
	while ((bytes_read=read(certificate_file, buffer, buffer_size))>0) 
		bufptr += bytes_read;

	if (!strcmp(argv[1],"-pem")) {
		unsigned char* pem_buffer = buffer;
		buffer = (char*)malloc(buffer_size);
		buffer_size = pem_decode(pem_buffer, buffer);
		free(pem_buffer);
	}

	// parse
	init_x509_certificate(&certificate);
	if ((error_code=parse_x509_certficate(buffer, buffer_size, &certificate))) {
		printf("Error %d parsing certificate \n",error_code);
	} else {
		printf("X509 Certficate:\n");
		display_x509_certificate(&certificate);
// TODO
#if 0
		// assume its a sefl signed cert. try to validate it
		switch(certificate.algorithm) {
		case md5WithRsaEncryption:
		case sha1WithRsaEncryption:
			if (validate_certificate_rsa(&certificate,
						     &certificate.tbsCertificate.subjectPublicKeyInfo.rsa_public_key))
			{
				printf("Certficate is a valid self-signed certficate\n");
					
			}
			else
			{
				printf("Certficate is corrupt or not self signed\n");
			}		    
			break;
		}
#endif		
	}

	free_x509_certficate(&certificate);
	free(buffer);
	return 0;
}

#endif
